import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
from util.neural_network import sigmoid_activation
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

def show():
    st.markdown('<h2 class="sub-header">二分类实战</h2>', unsafe_allow_html=True)

    st.markdown("""
    <div class="content">
        <p>本实战演示使用逻辑回归（Logistic Regression）解决二分类问题。</p>
        <p>我们将使用Sigmoid函数作为激活函数，交叉熵作为损失函数，并通过梯度下降法优化模型参数。</p>
    </div>
    """, unsafe_allow_html=True)

    # 代码示例
    st.subheader("二分类代码实现")
    st.code("""
import numpy as np
import matplotlib.pyplot as plt

# 定义两类散点
class1_points = np.array([[1.9, 1.2], [1.5, 2.1], [1.9, 0.5], [1.5, 0.9], 
                         [0.9, 1.2], [1.1, 1.7], [1.4, 1.1]])
class2_points = np.array([[3.2, 3.2], [3.7, 2.9], [3.2, 2.6], [1.7, 3.3], 
                         [3.4, 2.6], [4.1, 2.3], [3.0, 2.9]])

# 合并数据和标签
x1 = np.concatenate((class1_points[:, 0], class2_points[:, 0]), axis=0)
x2 = np.concatenate((class1_points[:, 1], class2_points[:, 1]), axis=0)
label = np.concatenate((np.zeros(len(class1_points)), np.ones(len(class2_points))), axis=0)

# 模型函数
def f(w1, w2, x1, x2, b):
    z = w1 * x1 + w2 * x2 + b
    a = sigmoid(z)
    return a

# 损失函数
def loss_fn(a, label):
    return -np.mean(label * np.log(a) + (1 - label) * np.log(1 - a))

# 训练过程
# ... 训练代码 ...
    """, language="python")

    # 实战演示
    st.subheader("二分类实战演示")

    # 定义两类散点
    class1_points = np.array([[1.9, 1.2],
                              [1.5, 2.1],
                              [1.9, 0.5],
                              [1.5, 0.9],
                              [0.9, 1.2],
                              [1.1, 1.7],
                              [1.4, 1.1]])

    class2_points = np.array([[3.2, 3.2],
                              [3.7, 2.9],
                              [3.2, 2.6],
                              [1.7, 3.3],
                              [3.4, 2.6],
                              [4.1, 2.3],
                              [3.0, 2.9]])

    x1 = np.concatenate((class1_points[:, 0], class2_points[:, 0]), axis=0)
    x2 = np.concatenate((class1_points[:, 1], class2_points[:, 1]), axis=0)
    label = np.concatenate(
        (np.zeros(len(class1_points)), np.ones(len(class2_points))),
        axis=0
    )

    # 期望函数（数学模型）
    def f(w1, w2, x1, x2, b):
        z = w1 * x1 + w2 * x2 + b
        a = sigmoid_activation(z)
        return a

    # 损失函数
    def loss_fn(a, label):
        return -np.mean(label * np.log(a) + (1 - label) * np.log(1 - a))

    # 超参数设置
    col1, col2, col3 = st.columns(3)
    with col1:
        lr = st.slider("学习率", 0.01, 1.0, 0.1, 0.01)
    with col2:
        Epochs = st.slider("训练轮数", 100, 5000, 1000, 100)
    with col3:
        show_animation = st.checkbox("显示训练动画", value=False)

    # 参数初始化
    w1 = 0
    w2 = 0
    b = 0

    # 训练按钮
    if st.button("开始训练"):
        progress_bar = st.progress(0)
        status_text = st.empty()

        # 准备画图
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
        epoch_list = []
        loss_list = []

        # 创建占位符用于动态更新图表
        chart_placeholder = st.empty()

        # 循环训练
        for epoch in range(Epochs):
            # 前向传播
            a = f(w1, w2, x1, x2, b)

            # 计算损失
            loss = loss_fn(a, label)

            # 反向传播（求导）
            deda = (label - a) / (a * (1 - a))
            dadz = a * (1 - a)
            dzdw1 = x1
            dzdw2 = x2
            dzdb = 1

            gd_w1 = -np.mean(deda * dadz * dzdw1)
            gd_w2 = -np.mean(deda * dadz * dzdw2)
            gd_b = -np.mean(deda * dadz * dzdb)

            # 更新参数
            w1 = w1 - lr * gd_w1
            w2 = w2 - lr * gd_w2
            b = b - lr * gd_b

            epoch_list.append(epoch)
            loss_list.append(loss)

            # 更新进度条
            progress = (epoch + 1) / Epochs
            progress_bar.progress(progress)
            status_text.text(f"训练中: {epoch + 1}/{Epochs}, 损失: {loss:.4f}")

            # 定期更新图表
            if show_animation and (epoch == 0 or (epoch + 1) % 10 == 0 or epoch == Epochs - 1):
                # 绘制散点分类图
                ax1.clear()
                ax1.scatter(class1_points[:, 0], class1_points[:, 1], color="r", label="类别 0")
                ax1.scatter(class2_points[:, 0], class2_points[:, 1], color="b", label="类别 1")

                # 绘制决策边界
                x1_min, x1_max = x1.min() - 0.5, x1.max() + 0.5
                x2_min, x2_max = -(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2
                ax1.plot([x1_min, x1_max], [x2_min, x2_max], color="g", label="决策边界")

                ax1.set_xlim(x1_min, x1_max)
                ax1.set_ylim(x2.min() - 0.5, x2.max() + 0.5)
                ax1.set_xlabel('x1')
                ax1.set_ylabel('x2')
                ax1.set_title('分类结果')
                ax1.legend()
                ax1.grid(True)

                # 绘制损失下降图
                ax2.clear()
                ax2.plot(epoch_list, loss_list, color="purple")
                ax2.set_xlabel('Epoch')
                ax2.set_ylabel('Loss')
                ax2.set_title('损失下降曲线')
                ax2.grid(True)

                # 显示图表
                chart_placeholder.pyplot(fig)
                # 短暂暂停以显示动画效果
                plt.pause(0.01)

        # 训练完成后显示最终结果
        if not show_animation:
            # 绘制最终散点分类图
            ax1.clear()
            ax1.scatter(class1_points[:, 0], class1_points[:, 1], color="r", label="类别 0")
            ax1.scatter(class2_points[:, 0], class2_points[:, 1], color="b", label="类别 1")

            # 绘制决策边界
            x1_min, x1_max = x1.min() - 0.5, x1.max() + 0.5
            x2_min, x2_max = -(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2
            ax1.plot([x1_min, x1_max], [x2_min, x2_max], color="g", label="决策边界")

            ax1.set_xlim(x1_min, x1_max)
            ax1.set_ylim(x2.min() - 0.5, x2.max() + 0.5)
            ax1.set_xlabel('x1')
            ax1.set_ylabel('x2')
            ax1.set_title('分类结果')
            ax1.legend()
            ax1.grid(True)

            # 绘制损失下降图
            ax2.clear()
            ax2.plot(epoch_list, loss_list, color="purple")
            ax2.set_xlabel('Epoch')
            ax2.set_ylabel('Loss')
            ax2.set_title('损失下降曲线')
            ax2.grid(True)

            # 显示图表
            chart_placeholder.pyplot(fig)

        # 显示最终参数
        st.success(f"训练完成! 最终参数: w1={w1:.4f}, w2={w2:.4f}, b={b:.4f}")

        # 模型评估
        st.subheader("模型评估")

        # 计算准确率
        a = f(w1, w2, x1, x2, b)
        y_pre = (a >= 0.5).astype(int)
        acc = np.mean(label == y_pre)
        st.write(f"准确率：{acc * 100:.2f}%")

        # 计算F1分数
        def f1_score(TP, FP, FN):
            precision = TP / (TP + FP) if (TP + FP) != 0 else 0.0
            recall = TP / (TP + FN) if (TP + FN) != 0 else 0.0
            if precision + recall == 0:
                return 0.0
            return 2 * precision * recall / (precision + recall)

        TP = np.sum((label == 1) & (y_pre == 1))
        FP = np.sum((label == 0) & (y_pre == 1))
        FN = np.sum((label == 1) & (y_pre == 0))
        TN = np.sum((label == 0) & (y_pre == 0))

        f1 = f1_score(TP, FP, FN)
        st.write(f"F1分数：{f1:.4f}")

        # 显示混淆矩阵
        st.write("混淆矩阵:")
        confusion_matrix = np.array([
            [TN, FP],
            [FN, TP]
        ])

        fig_cm, ax_cm = plt.subplots(figsize=(6, 4))
        im = ax_cm.imshow(confusion_matrix, cmap="Blues")

        # 添加数值标签
        for i in range(2):
            for j in range(2):
                ax_cm.text(j, i, confusion_matrix[i, j],
                           ha="center", va="center", color="black", fontsize=16)

        ax_cm.set_xticks([0, 1])
        ax_cm.set_yticks([0, 1])
        ax_cm.set_xticklabels(['预测负类', '预测正类'])
        ax_cm.set_yticklabels(['真实负类', '真实正类'])
        ax_cm.set_title('混淆矩阵')
        plt.colorbar(im, ax=ax_cm)

        st.pyplot(fig_cm)

        # 模型测试
        st.subheader("模型测试")
        st.write("输入测试数据点 (格式: x1,x2)，每行一个点:")

        test_input = st.text_area("测试数据", "1.5,1.5\n3.5,2.5")
        test_points = []

        for line in test_input.strip().split('\n'):
            if line:
                try:
                    x1_val, x2_val = map(float, line.split(','))
                    test_points.append([x1_val, x2_val])
                except:
                    st.error(f"无效输入: {line}")

        if test_points:
            test_points = np.array(test_points)
            a_test = f(w1, w2, test_points[:, 0], test_points[:, 1], b)
            test_pre = (a_test >= 0.5).astype(int)

            st.write("测试结果:")
            for i, point in enumerate(test_points):
                st.write(f"点 ({point[0]}, {point[1]}) -> 预测类别: {test_pre[i]} (概率: {a_test[i]:.4f})")